/**
 * 
 */
package com.celephais.common.server.service;

import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.celephais.common.client.model.DataFilter;
import com.celephais.common.client.model.TransferObject;
import com.celephais.common.client.service.ObjectAccessService;
//import com.celephais.workout.client.models.TableColumn;
import com.celephais.common.server.JDOPersistence.PMF;
import com.celephais.common.server.model.PersistentObject;
//import com.celephais.workout.server.models.Exercise;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.repackaged.org.apache.commons.logging.Log;
import com.google.appengine.repackaged.org.apache.commons.logging.LogFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * @author Eugene Shen
 *
 */
public abstract class ObjectAccessServiceImpl<T extends TransferObject> extends
	RemoteServiceServlet implements ObjectAccessService<T> {

	private static final long serialVersionUID = -1022297658018941012L;
	private static final Log logger = LogFactory.getLog(ObjectAccessServiceImpl.class);
	
	/**
	 * Create a Query object for queries.  The simplest form will probably be:
	 * return PMF.getFactory().getPersistenceManager().newQuery(T.class);
	 * @return A Query object for queries.
	 */
	protected abstract Query newQuery(DataFilter[] filters);

	/**
	 * Converts a single TransferObject to at least one PersistentObject
	 * that can be persisted by JDO.
	 * There is a one-to-many mapping here because we could potentially
	 * have a transfer object that is a composite of several persistent
	 * objects.  The add, delete and update operations all operate on a
	 * single TransferObject, but the DAO-level ramification may be
	 * more complex.
	 * @param transObj The TransferObject.
	 * @return A bunch of objects that can be persisted.
	 */
	protected abstract List<? extends PersistentObject> toPersistentObjects(T transObj);
	
	/**
	 * Converts a bunch of Persistable Objects to a bunch of
	 * TransferObjects.
	 * Even though this is a many-to-many mapping, you would almost always
	 * end up with no more TransferObjects than the Persistent Objects
	 * passed in.
	 * @param persistObjs The Persistable Objects (typically from a query).
	 * @return The converted TransferObjects.
	 */
	protected abstract List<T> toTransObjects(List<?> persistObjs);
	
	protected abstract boolean canAdd(T obj);
	protected abstract boolean canUpdate(T obj);
	protected abstract boolean canDelete(T obj);
	
	@Override
	public boolean add(T obj) {
		if (canAdd(obj)) {
			PersistenceManager pm = PMF.getFactory().getPersistenceManager();
			List<?> persistObjs = toPersistentObjects(obj);
			boolean okay = true;
			if (persistObjs != null && !persistObjs.isEmpty()) {
				pm.makePersistentAll(persistObjs);
			}
			else {
				okay = false;
			}
			pm.close();
			return okay;
		}
		else {
			logger.error("Cannot add object: " + obj.getName());
			return false;
		}
	}

	@Override
	public boolean delete(T obj) {
		if (canDelete(obj)) {
			PersistenceManager pm = PMF.getFactory().getPersistenceManager();
			List<? extends PersistentObject> persistObjs = toPersistentObjects(obj);
			boolean okay = true;
			if (persistObjs != null && !persistObjs.isEmpty()) {
				// Can't do this -- we'd get a "Transient instances cant be
				// deleted." error...
				// pm.deletePersistentAll(persistObjs);
				// Not very efficient, but...  Any better way?
				for (PersistentObject po : persistObjs) {
					Key key = po.getKey();
					if (key != null) {
						Query q = pm.newQuery(po.getClass());
						q.setFilter("key == '" + KeyFactory.keyToString(key) + "'");
						q.deletePersistentAll();
					}
				}
			}
			else {
				okay = false;
			}
			pm.close();
			return okay;
		}
		else {
			logger.error("Cannot delete object: " + obj.getName());
			return false;
		}
	}

	@Override
	public int getCount(DataFilter[] filters) {
		// TODO: Add filter support.
		
		Query query = newQuery(filters);
		query.setResult("count(this)");
		Object queryRes = query.execute();
		if (queryRes instanceof List<?>) {
			List<?> resList = (List<?>)queryRes;
			// Should be 1; should be Integer.
			if (resList.size() == 1) {
				if (resList.get(0) instanceof Integer) {
					return (Integer)resList.get(0);
				}
				else {
					logger.error("Select count result is not an Integer?!  Result class: " + resList.get(0).getClass().getCanonicalName());
					return -3;
				}
			}
			else {
				logger.error("Select count result size does not equal 1?!  resList.size: " + resList.size());
				return -2;
			}
		}
		else {
			logger.error("Select count query result is NOT a List?!  Query Result class: " + queryRes.getClass().getCanonicalName());
			return -1;
		}
	}

	@Override
	public List<T> getObjects(int startRow, int rowsCount,
			DataFilter[] filters, String sortColumnName, boolean sortOrder) {
		// TODO: Add filter support, sort, etc.
		
		Query query = newQuery(filters);
		Object queryRes = query.execute();
		if (queryRes instanceof List<?>) {
			List<?> result = (List<?>)queryRes;
			return toTransObjects(result);
		}
		else {
			logger.error("Query returned result of unknown type: " + queryRes.getClass().getCanonicalName());
			return null;
		}
	}

	@Override
	public boolean update(T obj) {
		if (canUpdate(obj)) {
			PersistenceManager pm = PMF.getFactory().getPersistenceManager();
			List<?> persistObjs = toPersistentObjects(obj);
			boolean okay = true;
			if (persistObjs != null && !persistObjs.isEmpty()) {
				pm.makePersistentAll(persistObjs);
			}
			else {
				okay = false;
			}
			pm.close();
			return okay;
		}
		else {
			logger.error("Cannot update object: " + obj.getName());
			return false;
		}
	}

}
